package org.msb.accentis.data.pojo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

import org.msb.accentis.data.enumerations.CategoryType;

/**
 * The {@code Category} interface provides the ability to manage objects are used to categorise transactions or parts of transactions.
 * 
 * @author Marc Boudreau
 * 
 * @since 1.0
 */
@Entity
@NamedQueries({
		@NamedQuery(name = "Category.getCategoriesByType", query = "SELECT c FROM Category c WHERE c.categoryType = :type AND c.parentCategory IS NULL"),
		@NamedQuery(name = "Category.getAllCategories", query = "SELECT c FROM Category c WHERE c.parentCategory IS NULL ORDER BY c.categoryType, c.name") })
public class Category extends NamedObject {

	@ManyToOne(cascade = CascadeType.ALL)
	private Category parentCategory;

	@Basic
	private CategoryType categoryType = CategoryType.INCOME;

	@OneToMany(cascade = CascadeType.ALL)
	@OrderBy
	private List<Category> subCategories = new ArrayList<Category>();

	/**
	 * Returns the associated {@code Category} instance as this object's parent.
	 * 
	 * @return The parent {@code Category} instance, or {@code null} if this object has no parent.
	 */
	public Category getParentCategory() {
		return parentCategory;
	}

	/**
	 * Sets the parent of this object to the provided {@code Category} object.
	 * 
	 * @param value
	 *            The {@code Category} object to set as the parent.
	 */
	public void setParentCategory(Category value) {
		parentCategory = value;

		if (null != value) {
			categoryType = value.getCategoryType();
			int pos = Collections.binarySearch(value.getSubCategories(), this);
			if (0 > pos)
				value.getSubCategories().add(-pos - 1, this);

		}
	}

	/**
	 * Returns the {@code Category} objects that have this object set as their parent.
	 * 
	 * @return A {@link List} of {@code Category} objects.
	 */
	public List<Category> getSubCategories() {
		return subCategories;
	}

	/**
	 * Returns an instance of the {@link CategoryType} enumeration that describes the type of this category.
	 * 
	 * @return A {@link CategoryType} instance.
	 */
	public CategoryType getCategoryType() {
		return categoryType;
	}

	/**
	 * Indicates whether this category's type represents the {@link CategoryType#INCOME} enumeration value.
	 * 
	 * @return A {@code boolean} value indicating whether this is an income category.
	 */
	public boolean isIncome() {
		return categoryType.equals(CategoryType.INCOME);
	}

	/**
	 * Indicates whether this category's type represents the {@link CategoryType#EXPENSE} enumeration value.
	 * 
	 * @return A {@code boolean} value indicating whether this is an expense category.
	 */
	public boolean isExpense() {
		return categoryType.equals(CategoryType.EXPENSE);
	}

	/**
	 * Sets the type of this category to the specified {@link CategoryType} instance.
	 * 
	 * @param value
	 *            The {@link CategoryType} instance to set as this category's type.
	 */
	public void setCategoryType(CategoryType value) {
		categoryType = value;

		for (Category subCategory : subCategories)
			subCategory.setCategoryType(value);
	}

	/**
	 * Indicates whether this category is a sub-category, meaning it has a parent set.
	 * 
	 * @return A {@code boolean} value indicating if this category is a sub-category.
	 */
	public boolean isSubCategory() {
		return null != parentCategory;
	}
}
